feat(aicore): transparent TLS mode and reactive credential reload - #256
feat(aicore): transparent TLS mode and reactive credential reload#256tiagoek wants to merge 6 commits into
Conversation
…ature Address reviewer feedback on PR #256: 1. Remove reload_aicore_credentials() wrapper — inline set_aicore_config() directly in the except AuthenticationError blocks. The wrapper added a named function for a single call; inlining is simpler and clearer. 2. Remove transparent TLS feature (AICORE_TRANSPARENT_TLS env var, _is_transparent_tls(), conditional client_secret handling in set_aicore_config()). This feature is blocked on an upstream LiteLLM PR and is not needed for the credential rotation fix. Nicole flagged that it belongs in a future secrets-resolver refactor. Behavior unchanged: AuthenticationError still triggers set_aicore_config() + retry, completely transparent to callers.
Production validation — proactive credential reload (
|
| Pod | Image | Threads in PID 1 |
|---|---|---|
Pre-fix (6ad5b0854983) |
0.3.3-20260825... (no watcher call) |
14 |
Post-fix (1858f9060d95) |
0.3.3-20260825200836_fd446f2+1 |
15 |
The extra thread is aicore-secret-watcher (daemon, polls /etc/secrets/appfnd/aicore/aicore-instance/ mtime every 30 s). It will call set_aicore_config() on the next kubelet symlink-swap rotation without requiring a pod restart.
autonomous-documentation-org unit tests: 286 passed, 1 skipped — coverage 90.51%
E2E validation — reactive credential reload (live pod test)Environment: Kyma managed runtime, namespace Test script
Execution outputWhat this proves
|
…ature Address reviewer feedback on PR #256: 1. Remove reload_aicore_credentials() wrapper — inline set_aicore_config() directly in the except AuthenticationError blocks. The wrapper added a named function for a single call; inlining is simpler and clearer. 2. Remove transparent TLS feature (AICORE_TRANSPARENT_TLS env var, _is_transparent_tls(), conditional client_secret handling in set_aicore_config()). This feature is blocked on an upstream LiteLLM PR and is not needed for the credential rotation fix. Nicole flagged that it belongs in a future secrets-resolver refactor. Behavior unchanged: AuthenticationError still triggers set_aicore_config() + retry, completely transparent to callers.
439f8d8 to
ccadb06
Compare
Introduces two security improvements for AI Core credential handling: 1. Transparent TLS mode (AICORE_TRANSPARENT_TLS=true): when active, set_aicore_config() skips writing AICORE_CLIENT_SECRET to os.environ and removes any stale value. The infrastructure sidecar proxy adds the mTLS certificate transparently on the SDK's behalf — no secret material needed in the agent process. Addresses HASI2026203 / SEC-309 (credentials exposed as env vars with excessive scope). 2. Reactive credential reload on AuthenticationError: completion() and acompletion() now intercept litellm.AuthenticationError, re-read credentials from the mounted secret volume, and retry once. Covers client_secret rotation and mTLS certificate rotation (cert-manager updates the volume file; the next failed token refresh triggers the reload) without requiring a pod restart. Relates-to: AFSDK-4306
…ature Address reviewer feedback on PR #256: 1. Remove reload_aicore_credentials() wrapper — inline set_aicore_config() directly in the except AuthenticationError blocks. The wrapper added a named function for a single call; inlining is simpler and clearer. 2. Remove transparent TLS feature (AICORE_TRANSPARENT_TLS env var, _is_transparent_tls(), conditional client_secret handling in set_aicore_config()). This feature is blocked on an upstream LiteLLM PR and is not needed for the credential rotation fix. Nicole flagged that it belongs in a future secrets-resolver refactor. Behavior unchanged: AuthenticationError still triggers set_aicore_config() + retry, completely transparent to callers.
- Add watch_aicore_config() daemon thread that polls secret directory mtime every 30s; on change calls set_aicore_config() proactively before LiteLLM's cached OAuth token expires (avoids 401 entirely) - Add _get_secret_dir_mtime() helper — returns 0.0 on OSError so missing dirs are handled safely - Fix ruff format: add blank line after local imports inside except blocks in completion.py (sync and async paths) - Add test_aicore_watcher.py (10 cases) and test_credential_rotation_flow.py (7 cases) covering watcher unit behavior and the LiteLLM env-update contract
- Remove __wrapped__ introspection in test_credential_rotation_flow that caused ty call-non-callable error; watcher call is already verified via reloaded.wait() - Fix trailing blank lines in test_aicore.py (end-of-file-fixer) - Bump version 0.38.0 → 0.41.0 (new public API: watch_aicore_config)
ccadb06 to
10bfdc8
Compare
There was a problem hiding this comment.
Would be possible to add a unit test with lang graph?
Checking the template I don't see our completion or acompletion being used. Would be important to map if there is any in the agent itself that LoBs will have to do, or if it is just a simper version bump.

Description
This PR addresses two security concerns with how the
aicoremodule handles credentials at runtime.1. Reactive credential reload on
AuthenticationError✅ completecompletion()andacompletion()now interceptlitellm.AuthenticationError, re-read credentials from the mounted secret volume viareload_aicore_credentials(), and retry the call once. This covers credential rotation scenarios (client secret rotation by the platform) without requiring a pod restart. If the retry also fails, the error propagates normally — no retry loop.reload_aicore_credentials()is exported as a public function for callers that need to trigger a manual reload.This feature is independently complete and works today with no additional changes.
2. Transparent TLS mode (⚠️ SDK complete — pending LiteLLM upstream
AICORE_TRANSPARENT_TLS)Adds opt-in support for infrastructure-managed mTLS authentication. The mechanism:
AICORE_TRANSPARENT_TLS=truein the pod environmentset_aicore_config()skips writingAICORE_CLIENT_SECRETtoos.environand actively removes any stale value already presentLiteLLM upstream dependency: The current public litellm
validate_credentials()requires exactly one credential mode (client_secret,cert_str+key_str, orcert_file_path+key_file_path). A 4th mode (transparent_tls=True) is needed to allow a no-credential token request where the sidecar provides the cert. Until that upstream change lands andlitellmminimum version is bumped inpyproject.toml,AICORE_TRANSPARENT_TLS=truewill result in aValueErrorfrom LiteLLM on the first completion call.Alternative today: Use proxy mode or destination mode from PR #271, which do not require the LiteLLM upstream change and address CVE 9.9 for the majority of agent deployments.
Any stale
AICORE_CLIENT_SECRETalready present in the environment is explicitly removed when transparent TLS mode is active, preventing accidental reuse.Related Issues
Type of Change
How to Test
Reactive credential reload (works today):
set_aicore_config()followed bycompletion()successfullyAuthenticationError(e.g. revoke the current token or wait for expiry)completion()call succeeds without a pod restartTransparent TLS mode (SDK side only — LiteLLM upstream pending):
AICORE_TRANSPARENT_TLS=truein the environment before callingset_aicore_config()AICORE_CLIENT_SECRETis not present inos.environafter the callAICORE_CLIENT_ID,AICORE_AUTH_URL,AICORE_BASE_URLare still set normallylitellm.completion()will still raiseValueErrorin transparent TLS modeUnit tests:
python -m pytest tests/aicore/unit/ -v # Expected: 65 passedChecklist
Breaking Changes
None. All changes are additive or opt-in:
AICORE_TRANSPARENT_TLS— requires explicit opt-in; default behavior is unchangedreload_aicore_credentials()— new public function, no existing callers affectedAuthenticationError— same exception type propagates if retry also fails; callers that catchAuthenticationErrormay observe a slight delay before receiving it (one additional attempt), but the contract is unchangedAdditional Notes
Dependency on LiteLLM upstream:
validate_credentials()inlitellm/llms/sap/credentials.pyneeds a 4th bypass mode for transparent TLS. The code change is minimal — add an optionaltransparent_tls: bool = Falseparameter that skips the credential requirement check when the sidecar handles authentication. A separate PR toBerriAI/litellmwill be submitted for this.Stacked PRs:
feat/aicore-clear-client-secret) — clearsAICORE_CLIENT_SECRETfrom env after first token acquisitionfeat/aicore-proxy-routing) — Option 3: proxy routing + BTP Destination Service mode (does not require the LiteLLM upstream change)